Skip to main content
  1. Posts/

PowerShell [Universal] Adventures

·9 mins·

Intro #

It has been a while since my last post, so let’s start with something new-ish!

Normally, I am not a huge fan of using PowerShell for GUI building, but I recently came across PowerShell Universal — out of necessity for a project — and I thought I’d write up my mostly positive experience with it. (Disclaimer: I am not affiliated with them!)

Installation #

I am going to largely skip this, as the documentation is quite clear on the process. In a nutshell, I installed the community edition (and the matching extension) to work on this project. It was mostly a trivial affair.

I also created a dedicated service account for this in Active Directory (not for the service; that runs as the local system—more for AD-related tasks).

A simple AD tool #

In this blog (which might or might not grow into a series), I built a tool to manage simple tasks of an AD administrator, such as managing user accounts (one could say, this is something trivial, but I thought I learn the tool with something familiar!)

Full disclosure: this post largely follows JackedProgrammer’s guide, so feel free to follow that if you prefer.

Define endpoints #

Anyway, once I got everything installed, I went ahead and created a number of API endpoints:

endpoints1 endpoints2

Write API code #

I created these endpoints via the GUI, so the code here focuses only on the actions we want to perform in Active Directory:

  • Getting details of a user account:
Import-Module ActiveDirectory

$ADCredential = $secret:ADCredentialPRD
$ADServer = "<  your domain controller >"
# $username = # provided by the :username

$splat = @{
    Identity   = $username
    Server     = $ADServer
    Credential = $ADCredential
}

$user = Get-ADUser  -Properties * @splat

$result = [PSCustomObject]@{
    Name                   = $user.Name
    UserName               = $user.SamAccountName
    UserPrincipalName      = $user.UserPrincipalName
    EmployeeID             = $user.EmployeeID
    Title                  = $user.Title
    Enabled                = $user.Enabled
    PasswordExpired        = $user.PasswordExpired
    LockedOut              = $user.LockedOut
    LastBadPasswordAttempt = $user.LastBadPasswordAttempt
}

$result
  • getting groups of a user account:
Import-Module ActiveDirectory

$ADCredential = $secret:ADCredentialPRD
$ADServer = "<  your domain controller >"
# $UserName = # provided by the :UserName

$splat = @{
    Identity   = $UserName
    Server     = $ADServer
    Credential = $ADCredential
}

$Result = @()


$groups = Get-ADPrincipalGroupMembership @splat

foreach ($group in $groups) {
    $GroupDetails = Get-ADGroup -Identity $group -Properties * -Server $ADServer
    $Entry = [PSCustomObject]@{
        Name          = $group.Name
        GroupCategory = $group.GroupCategory.ToString()
        GroupScope    = $GroupDetails.GroupScope.ToString()
        Description   = $GroupDetails.Description
    }
    $Result += $Entry
}

$Result
  • unlock a user account:
Import-Module ActiveDirectory

$ADCredential = $secret:ADCredentialPRD
$ADServer = "<  your domain controller >"
# $UserName = # provided by the :UserName

$splat = @{
    Identity   = $UserName
    Server     = $ADServer
    Credential = $ADCredential
}

Unlock-ADAccount -Confirm:$false @splat

$User = Get-ADUser -Properties lockedout @splat

$result = [PSCustomObject]@{
    UserName = $UserName
    LockedOut = $User.LockedOut
}

return $result
  • reset password of the user account:
Import-Module ActiveDirectory

$ADCredential = $secret:ADCredentialPRD
$ADServer = "<  your domain controller >"
# $UserName = # provided by the :UserName

$PasswordLength = 12 # default pw lenght, if no resultant password policy exists

$splat = @{
    Identity   = $UserName
    Server     = $ADServer
    Credential = $ADCredential
}

$PasswordPolicy = $null
$PasswordPolicy = Get-ADUserResultantPasswordPolicy  @splat

# overide password length if the user has a policy for this
if ($PasswordPolicy) {
    $PasswordLength = $PasswordPolicy.MinPasswordLength
}

$UpperCaseSet = (65..90) | ForEach-Object { [char]$_ }
$LowerCaseSet = (97..122) | ForEach-Object { [char]$_ }
$NumericSet = (48..57) | ForEach-Object { [char]$_ }
$SpecialSet = (33, 35, 36, 37, 38, 42, 63) | ForEach-Object { [char]$_ }
$CharSet = $UpperCaseSet + $LowerCaseSet + $NumericSet + $SpecialSet

$PasswordPlainText = -join (Get-Random -Count $PasswordLength -InputObject $CharSet)
$Password = $PasswordPlainText | ConvertTo-SecureString -AsPlainText -Force

$Password
Set-ADAccountPassword -NewPassword $Password -Reset @splat
Set-ADUser -ChangePasswordAtLogon $true -PasswordNeverExpires $false @splat

$result = [PSCustomObject]@{
    UserName = $UserName
    NewPassword = $PasswordPlainText
}

return $result
  • enable the user account:

Import-Module ActiveDirectory

$ADCredential = $secret:ADCredentialPRD
$ADServer = "<  your domain controller >"
# $UserName = # provided by the :UserName

$splat = @{
    Identity   = $UserName
    Server     = $ADServer
    Credential = $ADCredential
}

Set-ADUser -Enabled $true -Confirm:$false @splat

$User = Get-ADUser -Properties Enabled @splat
$result = [PSCustomObject]@{
    UserName = $UserName
    Enabled  = $User.Enabled
}

return $result
  • or disable the user account:
Import-Module ActiveDirectory

$ADCredential = $secret:ADCredentialPRD
$ADServer = "<  your domain controller >"
# $UserName = # provided by the :UserName

$splat = @{
    Identity   = $UserName
    Server     = $ADServer
    Credential = $ADCredential
}

Set-ADUser -Enabled $false -Confirm:$false @splat

$User = Get-ADUser -Properties Enabled @splat
$result = [PSCustomObject]@{
    UserName = $UserName
    Enabled  = $User.Enabled
}

return $result

Admin Page #

Now that I had the actions for the API-s configured, I went away and tested each using PostMan (but really Invoke-Webrequest or curl could have done it) and ensured the actions work.

Then I have added the code for a page, to display information and interact with AD using the API-s.

toolpage1

This simple page:

  • allows you to input the name of a user (samAccountName needed!)
  • provided the user exists, it then
    • populates some basic information on the account (1)
    • pulls in the group memberships of the account (2)
    • and displays buttons for the basic acctions (2)

Important to note, the action buttons dynamicaly change:

  • if the account is locked - Unlock Account will be click-able
  • if the account is disabled - Enable Account will become click-able
  • similarly - as you can see on my very much enabled account - Disable Account is active if the account is enabled
  • Reset Password is always active, and selecting it displays in a modal (pop-up) the new random password generated

Before I forget: the page is actually two pages:

  • the main page:
New-UDApp -Title "Powershell Universal" -Pages @(
    Get-UDPage -Name "ADUserTool"
)
  • then the tool page:
New-UDGrid -Container -Content {
    ## Grid for search fields [grid is 12 unit wide!]
    New-UDGrid -Item -ExtraSmallSize 2 -Content {
        New-UDForm -SubmitText "Search" -Content {
            New-UDTextbox -Id "UserName" -Label "Username"
        } -OnSubmit {
            try {

                ## Collect user details via API calls
                $UserName = $EventData.Username
                $UserDetails = Invoke-RestMethod -Uri "http://localhost:5000/ad/getuser/$username" -Method Get
                $UserGroups = Invoke-RestMethod -Uri "http://localhost:5000/ad/getusergroups/$username" -Method Get

                ## Display user groups
                Set-UDElement -Id "Groups" -Properties @{
                    data = $UserGroups
                }

                ## Display basic user info
                $Properties = ($UserDetails | Get-Member | Where-Object MemberType -eq NoteProperty).Name
                foreach ($property in $Properties) {
                    Set-UDElement -Id "UserDetails$Property" -Properties @{
                        content = "$property : $($UserDetails.$property)"
                    }
                }

                ## Set button states based on user state
                Set-UDElement -Id "ResetPassword" -Properties @{
                    disabled = $false
                }
                if ($UserDetails.LockedOut -eq $true) {
                    Set-UDElement -Id "UnlockAccount" -Properties @{
                        disabled = $false
                    }
                }
                else {
                    Set-UDElement -Id "UnlockAccount" -Properties @{
                        disable = $true
                    }
                }
                if ($UserDetails.enabled -eq $true) {
                    Set-UDElement -Id "DisableAccount" -Properties @{
                        disabled = $false
                    }
                    Set-UDElement -Id "EnableAccount" -Properties @{
                        disabled = $true
                    }
                }
                else {
                    Set-UDElement -Id "DisableAccount" -Properties @{
                        disabled = $true
                    }
                    Set-UDElement -Id "EnableAccount" -Properties @{
                        disabled = $false
                    }
                }
            }
            catch {

                ## Error for non-existing user
                Show-UDModal -Content {
                    New-UDAlert -Severity Error -Text "UserName not found - $($_)"
                }

                ## Reset user details for non-existing user
                Set-UDElement -Id "Groups" -Properties @{
                    data = ""
                }
                Set-UDElement -Id "UserDetailsName" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsUserName" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsUserPrincipalName" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsEmployeeID" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsTitle" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsEnabled" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsPasswordExpired" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsLockedOut" -Properties @{
                    content = " "
                }
                Set-UDElement -Id "UserDetailsLastBadPasswordAttempt" -Properties @{
                    content = " "
                }

                ## Update buton states for non-existing user
                Set-UDElement -Id "ResetPassword" -Properties @{
                    disabled = $true
                }
                Set-UDElement -Id "UnlockAccount" -Properties @{
                    disabled = $true
                }
                Set-UDElement -Id "DisableAccount" -Properties @{
                    disabled = $true
                }
                Set-UDElement -Id "EnableAccount" -Properties @{
                    disabled = $true
                }
            }
        }
    }

    ## Grid for buttons
    New-UDGrid -Item -ExtraSmallSize 10 -Content {
        New-UDParagraph -Content {
            New-UDButton -Id "UnlockAccount" -Text "Unlock Account" -Disabled -OnClick {

                ## Select user account
                $UserName = (Get-UDElement -Id "UserDetailsUserName").content.split(':')[1].trim()

                ## Button action (process user)
                Invoke-RestMethod -Uri "http://localhost:5000/ad/unlockaccount/$UserName" -Method Post

                ## Display updated details
                $UserDetails = Invoke-RestMethod -Uri "http://localhost:5000/ad/getuser/$UserName" -Method Get
                $Properties = ($UserDetails | Get-Member | Where-Object MemberType -eq NoteProperty).Name
                foreach ($property in $Properties) {
                    Set-UDElement -Id "UserDetails$Property" -Properties @{
                        content = "$property : $($UserDetails.$property)"
                    }
                }

                ## Update button state post-action
                Set-UDElement -Id "UnlockAccount" -Properties @{
                    disabled = $true
                }

            }
            New-UDButton -Id "EnableAccount" -Text "Enable Account" -Disabled -OnClick {

                ## Select user account
                $UserName = (Get-UDElement -Id "UserDetailsUserName").content.split(':')[1].trim()

                ## Button action (process user)
                Invoke-RestMethod -Uri "http://localhost:5000/ad/enableaccount/$UserName" -Method Post

                ## Display updated details
                $UserDetails = Invoke-RestMethod -Uri "http://localhost:5000/ad/getuser/$UserName" -Method Get

                $Properties = ($UserDetails | Get-Member | Where-Object MemberType -eq NoteProperty).Name
                foreach ($property in $Properties) {
                    Set-UDElement -Id "UserDetails$Property" -Properties @{
                        content = "$property : $($UserDetails.$property)"
                    }
                }

                ## Update button states post-action
                Set-UDElement -Id "EnableAccount" -Properties @{
                    disabled = $true
                }
                Set-UDElement -Id "DisableAccount" -Properties @{
                    disabled = $false
                }

            }
            New-UDButton -Id "DisableAccount" -Text "Disable Account" -Disabled -OnClick {

                ## Select user account
                $UserName = (Get-UDElement -Id "UserDetailsUserName").content.split(':')[1].trim()

                ## Button action (process user)
                Invoke-RestMethod -Uri "http://localhost:5000/ad/disableaccount/$UserName" -Method Post

                ## Display updated details
                $UserDetails = Invoke-RestMethod -Uri "http://localhost:5000/ad/getuser/$UserName" -Method Get
                $Properties = ($UserDetails | Get-Member | Where-Object MemberType -eq NoteProperty).Name
                foreach ($property in $Properties) {
                    Set-UDElement -Id "UserDetails$Property" -Properties @{
                        content = "$property : $($UserDetails.$property)"
                    }
                }

                ## Update button states post-action
                Set-UDElement -Id "EnableAccount" -Properties @{
                    disabled = $false
                }
                Set-UDElement -Id "DisableAccount" -Properties @{
                    disabled = $true
                }

            }
            New-UDButton -Id "ResetPassword" -Text "Reset Password" -Disabled -OnClick {

                ## Select user account
                $UserName = (Get-UDElement -Id "UserDetailsUserName").content.split(':')[1].trim()

                ## Button action (process user)
                $NewPassword = Invoke-RestMethod -Uri "http://localhost:5000/ad/resetpassword/$UserName" -Method Post

                # Display pop-up with password
                Show-UDModal -Content {
                    New-UDAlert -Severity Info -Text "Password for  $($NewPassword.UserName) has been reset to: $($NewPassword.NewPassword)"
                }

            }
        }
    }
    ## Grid for User Details
    New-UDGrid -Item -ExtraSmallSize 5 -Content {
        New-UDHeading -Content { "User details" } -Size 2 -Color White
        New-UDElement -Id "UserDetails" -Tag "ul" -Content {
            New-UDElement -Id "UserDetailsName" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsUserName" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsUserPrincipalName" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsEmployeeID" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsTitle" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsEnabled" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsPasswordExpired" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsLockedOut" -Tag "li" -Content {}
            New-UDElement -Id "UserDetailsLastBadPasswordAttempt" -Tag "li" -Content {}
        }
    }
    ## Grid for Group Membership
    New-UDGrid -Item -ExtraSmallSize 5 -Content {
        New-UDHeading -Content {
            "Group Membership"
        } -Size 2 -Color White
        New-UDTable -Id "Groups" -Columns @(
            New-UDTableColumn -Property "Name" -Title "Name"
            New-UDTableColumn -Property "GroupCategory" -Title "Group Category"
            New-UDTableColumn -Property "GroupScope" -Title "Group Scope"
            New-UDTableColumn -Property "Description" -Title "Description"
        )
    }
}

As I put quite extensive comments along the blocks of the code, I let you explore it.